
package stratego.java.ssm.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;

import stratego.java.ssm.Board;
import stratego.java.ssm.piece.Piece;
import stratego.java.ssm.piece.PieceUtilities;

/**
 * <code>public class PieceSelector</code>
 * <p>
 * This class serves as a helper to the Game classes to help facilitate setting up the board.
 * </p>
 * 
 * @author Mark Aligbe
 * 
 */
public class PieceSelector implements ActionListener, ComponentListener
{
	private JDialog	                        dialog;
	private JPanel	                        panel;
	private Color[]	                        gradient;
	private Piece	                        currentPiece;
	/** The number and type of pieces both players are allotted */
	public HashMap<String, Integer>	        numPieces;
	private HashMap<String, SelectorButton>	buttonMaps;
	private int	                            playerNum;
	// A lock used to indicate that this piece is still selecting
	private Object	                        lock;
	// Used to determine when to display the done button
	private int	                            placedPieces, maxPieces;
	private HashMap<Point, Piece>	        pieceLocations;
	private BoardEventListener	            game;
	private Board	                        board;
	private GameCanvas	                    canvas;
	
	/**
	 * <code>public PieceSelector(Dimension size, int maxPieces)</code>
	 * <p>
	 * Constructs the PieceSelector with the given size and the global number of pieces in play for
	 * any player.
	 * </p>
	 * 
	 * @param size
	 *            - The size of the frame that this piece should hold.
	 * @param maxPieces
	 *            - The maximum number of pieces to handle. There may be less pieces than specified
	 *            due to rounding error in keeping the proportions right, and some pieces will not
	 *            appear if this number is too low.
	 * @param player
	 *            - An int representing the player number. Used to determine the color of the pieces
	 *            that are created.
	 * @param location
	 *            - A point representing the location to display the frame at. The upper left corner
	 *            of the frame is placed there.
	 * @param lock
	 *            - An Object used to lock the thread of the BoardEventListener object until
	 *            selection has completed.
	 * @param game
	 *            - A BoardEventListener whose boardClicked(BoardEvent e) will be called when
	 *            randomization is used.
	 * @param canvas
	 */
	public PieceSelector(Dimension size, int maxPieces, int player, Point location, Object lock,
	        BoardEventListener game, Board board, GameCanvas canvas, JFrame mainFrame)
	{
		playerNum = player;
		this.maxPieces = maxPieces;
		if (player == 1)
			gradient = PieceUtilities.redGradient;
		else
			gradient = PieceUtilities.blueGradient;
		// Make a new JFrame to hold the buttons
		dialog = new JDialog(mainFrame);
		dialog.setTitle("Selection");
		dialog.setSize(size);
		dialog.addComponentListener(this);
		// Initialize numPieces
		numPieces = new HashMap<String, Integer>(53, 0.9f);
		// Declare the number of pieces for each piece type. Yay declarative programming.
		numPieces.put("Marshal", (int) Math.round(1 / 40.0 * maxPieces));
		numPieces.put("General", (int) Math.round(1 / 40.0 * maxPieces));
		numPieces.put("Colonel", (int) Math.round(1 / 20.0 * maxPieces));
		numPieces.put("Major", (int) Math.round(3 / 40.0 * maxPieces));
		numPieces.put("Captain", (int) Math.round(1 / 10.0 * maxPieces));
		numPieces.put("Lieutenant", (int) Math.round(1 / 10.0 * maxPieces));
		numPieces.put("Sergeant", (int) Math.round(1 / 10.0 * maxPieces));
		numPieces.put("Miner", (int) Math.round(1 / 8.0 * maxPieces));
		numPieces.put("Scout", (int) Math.round(1 / 5.0 * maxPieces));
		numPieces.put("Spy", (int) Math.round(1 / 40.0 * maxPieces));
		numPieces.put("Bomb", (int) Math.round(3 / 20.0 * maxPieces));
		numPieces.put("Flag", 1);
		// Setup the button selector now that all pieces have been declared
		JPanel outerPanel = new JPanel(new BorderLayout());
		panel = new JPanel(new GridLayout((int) Math.round(numPieces.size() / 2.0), 2));
		JButton random = new JButton("Randomize");
		random.addActionListener(this);
		outerPanel.add(BorderLayout.NORTH, random);
		outerPanel.add(BorderLayout.CENTER, panel);
		dialog.getContentPane().add(outerPanel);
		buttonMaps = new HashMap<String, SelectorButton>();
		for (Map.Entry<String, Integer> set : numPieces.entrySet())
		{
			SelectorButton button = new SelectorButton(set.getKey(), set.getValue());
			button.addActionListener(this);
			buttonMaps.put(set.getKey(), button);
			panel.add(button);
		}
		dialog.setLocation(location);
		this.board = board;
		this.canvas = canvas;
		this.game = game;
		this.lock = lock;
	}
	
	/**
	 * <code>public void showFrame()</code>
	 * <p>
	 * Shows the frame belonging to this PieceSelector
	 * </p>
	 */
	public void showFrame()
	{
		dialog.setVisible(true);
	}
	
	/**
	 * <code>public void hideFrame()</code>
	 * <p>
	 * Hides the frame belonging to this PieceSelector
	 * </p>
	 */
	public void hideFrame()
	{
		dialog.setVisible(false);
	}
	
	/**
	 * <code>public void closeFrame()</code>
	 * <p>
	 * Destroys the frame belonging to this PieceSelector. This PieceSelector is consequently of no
	 * longer use.
	 * </p>
	 */
	private void closeFrame()
	{
		synchronized (lock)
		{
			lock.notify();
		}
		dialog.dispose();
	}
	
	/**
	 * <code>public Piece getSelectedPiece()</code>
	 * <p>
	 * Returns the Piece last clicked on by the user.
	 * </p>
	 * 
	 * @return An object <? extends Piece> or null if nothing has yet been selected.
	 */
	public Piece getSelectedPiece()
	{
		return currentPiece;
	}
	
	/**
	 * <code>public int getPlayer()</code>
	 * <p>
	 * Returns the player number this PieceSelector is currently providing for.
	 * </p>
	 * 
	 * @return An int: 1 for player 1, 2 for player 2.
	 */
	public int getPlayer()
	{
		return playerNum;
	}
	
	@Override
	/**Returns the currently selected piece by using reflection*/
	public void actionPerformed(ActionEvent arg0)
	{
		if (arg0.getActionCommand().equals("Done?"))
		{
			closeFrame();
			return;
		}
		else if (arg0.getActionCommand().equals("Randomize"))
		{
			randomize();
			return;
		}
		SelectorButton button = (SelectorButton) arg0.getSource();
		currentPiece = createPiece(button.getName());
	}
	
	private Piece createPiece(String name)
	{
		Piece piece = null;
		Class<? extends Piece> c = PieceUtilities.PIECES.get(name);
		try
		{
			piece = c.getConstructor((new Color[] {}).getClass()).newInstance((Object) gradient);
		}
		catch (InstantiationException e)
		{
			e.printStackTrace();
		}
		catch (IllegalAccessException e)
		{
			e.printStackTrace();
		}
		catch (IllegalArgumentException e)
		{
			e.printStackTrace();
		}
		catch (InvocationTargetException e)
		{
			e.printStackTrace();
		}
		catch (NoSuchMethodException e)
		{
			e.printStackTrace();
		}
		catch (SecurityException e)
		{
			e.printStackTrace();
		}
		return piece;
	}
	
	public void setPlayerPieceLocations(HashMap<Point, Piece> pieceLocations)
	{
		this.pieceLocations = pieceLocations;
	}
	
	private void randomize()
	{
		// Decouple the entrySet from the HashMap, as the HashMap will be modified during the
		// iteration (scratch that, it seems that the Map.Entry itself is linked, not the set
		// containing the object. Still copying it anyway, because we don't want to iterate over it)
		ArrayList<Map.Entry<String, Integer>> entrySet =
		        new ArrayList<Map.Entry<String, Integer>>(numPieces.entrySet());
		// Keep looping until we place each piece
		while (entrySet.size() > 0)
		{
			Map.Entry<String, Integer> mapping = entrySet.get(0);
			// Keep looping each piece until it's empty
			while (mapping.getValue() > 0)
			{
				// Make a new piece denoted by this mapping.
				Piece piece = createPiece(mapping.getKey());
				Random coord = new Random();
				int x = 0, y = 0;
				// The first time
				do
				{
					// x will range from 0 to boardLength
					x = coord.nextInt(board.getBoard().length);
					// If boardLength == 10, then y will range from 0-3 (which is what we want)
					y = coord.nextInt(board.getBoard().length / 2 - 1);
					if (playerNum == 1)
						// If boardLength==10, then y will range from 6-9 (it adds 6 to y)
						y += board.getBoard().length / 2 + 1;
				}
				while (pieceLocations.containsKey(new Point(x, y)));
				BoardEvent e = new BoardEvent(canvas, board, x, y, InputEvent.BUTTON1_MASK, piece);
				game.boardClicked(e);
			}
			entrySet.remove(0);
		}
	}
	
	/**
	 * <code>public void decrementPiece(Piece p)</code>
	 * <p>
	 * Reduces the value of some piece represented on this PieceSelector by 1. Removes the item from
	 * the piece selector if it reaches 0. Should be used when the player has selected a piece and
	 * wishes to place it on the board.
	 * </p>
	 * 
	 * @param p
	 *            - The piece whose value to decrement
	 */
	public void decrementPiece(Piece p)
	{
		SelectorButton button = buttonMaps.get(p.toString());
		button.setAmount(button.getAmount() - 1);
		numPieces.put(button.getName(), button.getAmount());
		currentPiece = createPiece(button.getName());
		if (button.getAmount() == 0)
		{
			panel.remove(button);
			panel.repaint();
			buttonMaps.remove(button.getName());
			numPieces.remove(button.getName());
			currentPiece = null;
		}
		placedPieces++;
		if (placedPieces == maxPieces)
		{
			JButton jbutton = new JButton("Done?");
			jbutton.addActionListener(this);
			panel.add(jbutton);
			panel.updateUI();
		}
	}
	
	/**
	 * <code>public void incrementPiece(Piece p)</code>
	 * <p>
	 * Increments the value of some piece represented by this PieceSelector. Should be used if the
	 * player decided not to place a piece after it was already placed.
	 * </p>
	 * 
	 * @param p
	 *            - The piece to increment on this board.
	 */
	public void incrementPiece(Piece p)
	{
		SelectorButton button = buttonMaps.get(p.toString());
		if (button == null)
		{
			button = new SelectorButton(p.toString(), 1);
			button.addActionListener(this);
			panel.add(button);
			panel.updateUI();
			buttonMaps.put(button.getName(), button);
		}
		else
			button.setAmount(button.getAmount() + 1);
		numPieces.put(button.getName(), button.getAmount());
		if (placedPieces == maxPieces)
		{
			for (Component comp : panel.getComponents())
			{
				if (comp instanceof JButton)
				{
					// We found the button we're looking for, now to remove it
					JButton butt = (JButton) comp;
					if (butt.getText() != null && butt.getText().equals("Done?"))
					{
						panel.remove(butt);
						panel.repaint();
						panel.updateUI();
					}
				}
			}
		}
		placedPieces--;
	}
	
	/**
	 * <code>class SelectorButton extends JButton</code>
	 * <p>
	 * A short modification of the JButton to set a custom text string and facilitate the
	 * incrementing and decrementing of values.
	 * </p>
	 * 
	 * @author Mark Aligbe
	 * 
	 */
	class SelectorButton extends JButton
	{
		
		private static final long	serialVersionUID	= 7590168584715115416L;
		private int		          amount;
		private final String		name;
		
		/**
		 * <code>public SelectorButton(String name, int amount)</code>
		 * <p>
		 * Constructs a JButton with HTML rendering whose text is: name + "(s) Remaining: " + amount
		 * </p>
		 * 
		 * @param name
		 *            - The name of the piece. Must correspond with an actual class name (i.e.
		 *            "Marshal" refers to MarshalPiece.java)
		 * @param amount
		 *            - The initial amount of pieces this button holds. Should be the maximum number
		 *            of this type of piece available.
		 */
		public SelectorButton(String name, int amount)
		{
			super("<html>" + name + "(s) Remaining: " + amount + "</html>");
			setName(name);
			this.amount = amount;
			this.name = name;
		}
		
		/**
		 * <code>public int getAmount()</code>
		 * <p>
		 * Gets the current number of Pieces this button has.
		 * </p>
		 * 
		 * @return The number of Pieces this button still has.
		 */
		public int getAmount()
		{
			return amount;
		}
		
		/**
		 * <code>public void setAmount(int newAmount)</code>
		 * <p>
		 * Sets the amount of this button to the specified amount (used for incrementing and
		 * decrementing).
		 * </p>
		 * 
		 * @param newAmount
		 *            - The new amount of items that this button should represent.
		 */
		public void setAmount(int newAmount)
		{
			amount = newAmount;
			this.setText("<html>" + name + "(s) Remaining: " + amount + "</html>");
		}
	}
	
	@Override
	/**This listener is only attached to the frame*/
	public void componentHidden(ComponentEvent e)
	{
		// Only attached to the frame
		if (dialog.isDisplayable())
			dialog.setVisible(true);
	}
	
	@Override
	public void componentMoved(ComponentEvent e)
	{}
	
	@Override
	public void componentResized(ComponentEvent e)
	{}
	
	@Override
	public void componentShown(ComponentEvent e)
	{}
}
